﻿using System;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;
using System.Collections.Generic;
using System.Reflection;
namespace TagChimp
{
    /// <summary>
    /// Creates a new instance of the TagChimp Client Service
    /// </summary>
    public class TagChimpClient
    {
        /// <summary>
        /// Gets or sets the Tag Chimp API key.
        /// </summary>
        /// <value>The API key.</value>
        /// <remarks></remarks>
        public string ApiKey { get; set; }
        /// <summary>
        /// Initializes a new instance of the <see cref="TagChimpClient"/> class.
        /// </summary>
        /// <param name="apiKey">Your TagChimp API key.</param>
        /// <exception cref="ArgumentException"/>
        /// <remarks></remarks>
        public TagChimpClient(string apiKey)
        {
            if (String.IsNullOrEmpty(apiKey))
                throw new ArgumentException("Api Key is null or empty.", "apiKey");
            ApiKey = apiKey;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="TagChimpClient"/> class.
        /// </summary>
        /// <param name="apikey">The TagChimp API Key.</param>
        /// <param name="secretkey">The TagChimp secret key.</param>
        public TagChimpClient(string apikey, string secretkey)
            : this(apikey)
        {

            if (String.IsNullOrEmpty(apikey))
                throw new ArgumentException("apikey is null or empty.", "apikey");
            if (String.IsNullOrEmpty(secretkey))
                throw new ArgumentException("secretkey is null or empty.", "secretkey");
            SecretKey = secretkey;
        }

        /// <summary>
        /// Initializes a new TagChimp Client instance.
        /// </summary>
        /// <remarks></remarks>
#if DEBUG
        public TagChimpClient()
        {

            ApiKey = "5952710984D89ACECA4C82";
        }
#endif

        /// <summary>
        /// Gets the episode info.
        /// </summary>
        /// <param name="filename">The filename.</param>
        /// <returns>EpisodeInfo populated from TagChimp webservice</returns>
        /// <exception cref="TagChimpHttpException"/>
        /// <exception cref="TagChimpTitleNotFoundException"/>
        /// <exception cref="TagChimpApiKeyMissingException"/>
        /// <exception cref="TagChimpApiKeyInvalidException"/>
        /// <remarks></remarks>
        public  EpisodeInfo     getEpisodeInfo(string filename)
        {
            var ei = ParseEpisodeFilename(filename);
            
            return getEpisodeInfo(ei);
        }
        /// <summary>
        /// Gets the episode info.
        /// </summary>
        /// <param name="ei">The ei.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public EpisodeInfo getEpisodeInfo(EpisodeInfo ei)
        {
            return MapXmlToObject(ei);
        }
        /// <summary>
        /// Gets or sets the secret key for posting to Tag Chimp.
        /// Optional only needed if you are going to be posting new data to Tag Chimp.
        /// </summary>
        /// <value>
        /// The secret key provided by Tag Chimp.
        /// </value>
        public string SecretKey { get; set; }
        /// <summary>
        /// Search TagChimp and return an unfiltered  list of movies.
        /// </summary>
        /// <param name="mi">The Movie to search for .</param>
        /// <returns></returns>
        public List<MovieInfo> Search(MovieInfo mi)
        { 
            var doc = getXmlFromWebService(mi);
            var yearfinder = new Regex(@"(?<year>\d{4})"); 

            var xmlresults = doc.SelectNodes("items/movie");
            var resultslist = new List<MovieInfo>();


            foreach (XmlNode r in xmlresults)
            {

                var m = new MovieInfo();


                var tags = r.SelectSingleNode("movieTags");
                var info = tags.SelectSingleNode("info");

                m.Title = info.SelectSingleNode("movieTitle").InnerText;
                m.Genre = info.SelectSingleNode("category").InnerText;
                m.Description = info.SelectSingleNode("longDescription").InnerText;
                m.CoverArt = tags.SelectSingleNode("coverArtLarge").InnerText;
                m.MediaType = "Movie";
                int year = 0;
                m.Rating = info.SelectSingleNode("rating").InnerText;

                var datefromxml = info.SelectSingleNode("releaseDate").InnerText; 
                string yrstr = yearfinder.Match(datefromxml).Value;
                int.TryParse(yrstr, out year);

                m.ReleaseYear = year;


                foreach ( XmlNode actor in info.SelectSingleNode("cast").ChildNodes)
                {
                    m.Cast.Add(actor.InnerText);
                }
                foreach (XmlNode director in info.SelectSingleNode("directors").ChildNodes)
                {
                    m.Directors.Add(director.InnerText);
                }
                foreach (XmlNode producer in info.SelectSingleNode("producers"))
                {
                    m.Producers.Add(producer.InnerText);
                }

                resultslist.Add(m);
            }



            return resultslist.ToList();
        
        }

        #region Episode Builder
        /// <summary>
        /// Maps the XML from the TagChimp web service to episode info.
        /// </summary>
        /// <param name="ei">The ei.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        private EpisodeInfo     MapXmlToObject(EpisodeInfo ei)
        {

            var doc = getXmlFromWebService(ei);
            var movietags = (from q in doc.DocumentElement.ChildNodes.Cast<XmlNode>()
                             select q.SelectSingleNode("/items/movie/movieTags")).FirstOrDefault();

            var info = movietags.SelectSingleNode("info");

            var tvtags = movietags.SelectSingleNode("television");

            ei.CoverArt = movietags.SelectSingleNode("coverArtLarge").InnerText; ;
            ei.Description = info.SelectSingleNode("longDescription").InnerText;
            ei.EpisodeName = info.SelectSingleNode("movieTitle").InnerText;
            ei.Genre = info.SelectSingleNode("category").InnerText;
            ei.MediaType = info.SelectSingleNode("kind").InnerText;
            ei.Network = tvtags.SelectSingleNode("network").InnerText;

            foreach (var n in info.SelectSingleNode("directors").ChildNodes)
            {
                ei.Directors.Add(((XmlNode)n).InnerText);
            }
            foreach (var n in info.SelectSingleNode("cast").ChildNodes)
            {
                ei.Cast.Add(((XmlNode)n).InnerText);
            }

            foreach (var n in info.SelectSingleNode("producers").ChildNodes)
            {
                ei.Producers.Add(((XmlNode)n).InnerText);
            }

            ei.Rating = info.SelectSingleNode("rating").InnerText; 
            
            return ei;
        }
        /// <summary>
        /// Parses the episode filename.
        /// </summary>
        /// <param name="filename">The episodes filename.</param>
        /// <exception cref="ArgumentException"/>
        /// <exception cref="FormatException"/>
        /// <returns></returns>
        /// <remarks></remarks>
        private EpisodeInfo     ParseEpisodeFilename(string filename)
        {
            
            if (String.IsNullOrEmpty(filename))
                throw new ArgumentException("filename is null or empty.", "filename");


            var r = new Regex(@"(([S,s])([0-9])([0-9])([E,e])([0-9])([0-9]))");

            
            var ep = Regex.Split(filename, r.ToString());

            string showname = "no match";
            var season = 0;
            var episode = 0;

            if (r.IsMatch(filename))
            {
                showname = new StringBuilder(ep[0]).Replace('.', ' ').ToString();
                season = Convert.ToInt32(ep[1].Substring(1, 2));
                episode = Convert.ToInt32(ep[1].Substring(4, 2));

            }
            else
            {
                throw new TagChimpMalformedFilenameException();
            }



            return new EpisodeInfo
            {
                EpisodeNumber = episode,
                SeasonNumber = season,
                Title = showname
            };

        }
        /// <summary>
        /// Builds the episode query URL.
        /// </summary>
        /// <param name="ei">The ei.</param>
        /// <exception cref="TagChimpApiKeyMissingException"/>
        /// <returns>TagChimp television query url</returns>
        /// <remarks></remarks>
        private string          BuildQueryUrl(EpisodeInfo ei)
        {
           

            if (String.IsNullOrEmpty(ApiKey))
                throw new TagChimpApiKeyMissingException();



            string url = String.Format("http://tagchimp.com/ape/lookup.php?token={0}&show={1}&season={2}&episode={3}&limit=1", 
                             ApiKey, 
                             new StringBuilder( ei.Title.Trim()).Replace(' ', '+'), 
                             ei.SeasonNumber, 
                             ei.EpisodeNumber);

            
            
            return  url;
        }
        /// <summary>
        /// Gets the XML from the TagChimp web service.
        /// </summary>
        /// <param name="ei">EpisodeInfo instance.</param>
        /// <exception cref="TagChimpTitleNotFoundException"/>
        /// <exception cref="TagChimpApiKeyInvalidException"/>
        /// <returns></returns>
        /// <remarks></remarks>
        private XmlDocument     getXmlFromWebService(EpisodeInfo ei)
        {

            
            Console.WriteLine("Retrieving Xml..");

            var doc = new XmlDocument();

            

            try
            {
               doc.Load(BuildQueryUrl(ei)); 
            }
            catch (WebException)
            {
                throw new TagChimpHttpException();
            }





            if (String.Compare(doc.DocumentElement.InnerText, "title not found", false) == 0)
                                
                throw new TagChimpTitleNotFoundException();

            else if (String.Compare(doc.DocumentElement.InnerText, "a valid token is required", false) == 0)
            {
                throw new TagChimpApiKeyInvalidException();
            }
            else
            {

                Console.WriteLine("Data Recieved Successfully!");
                return doc;
            
            }

        }
        #endregion
        #region Movie Builder

        /// <summary>
        /// Gets the movie info.
        /// </summary>
        /// <param name="filename">The filename.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public MovieInfo getMovieInfo(string filename)
        {
            var mi = ParseMovieFilename(filename);
            return MapXmlToObject(mi);
        }
        /// <summary>
        /// Gets the XML from web service.
        /// </summary>
        /// <param name="mi">The mi.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public  XmlDocument getXmlFromWebService(MovieInfo mi)
        {
            Console.WriteLine( "Retrieving Xml...");

            var doc = new XmlDocument();
            try
            {
                doc.Load(BuildQueryUrl(mi));
            }
            catch (WebException)
            {
                throw new TagChimpHttpException();
            }





            if (String.Compare(doc.DocumentElement.InnerText, "title not found", false) == 0)

                throw new TagChimpTitleNotFoundException();

            else if (String.Compare(doc.DocumentElement.InnerText, "a valid token is required", false) == 0)
            {
                throw new TagChimpApiKeyInvalidException();
            }
            else
            {

                Console.WriteLine("Data Recieved Successfully!");
                return doc;

            }



           
        }

        /// <summary>
        /// Maps the XML to given object.
        /// </summary>
        /// <param name="mi">MovieInfo Object to map.</param>
        /// <returns>populated MovieInfo Object</returns>
        /// <remarks></remarks>
        public MovieInfo MapXmlToObject(MovieInfo mi)
        {
            var filteredlist =
                from movies in Search(mi)
                where
                String.Compare(movies.Title.Replace(" ", string.Empty), mi.Title.Replace(" ", string.Empty), true) == 0 &&
                movies.ReleaseYear == mi.ReleaseYear
                select movies;

            if (filteredlist.Count() == 0)
                throw new TagChimpTitleNotFoundException();

            mi = getBestResult(filteredlist.ToList());

            return mi;
        }
        /// <summary>
        /// Gets the movie info.
        /// </summary>
        /// <param name="mi">The mi.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public MovieInfo getMovieInfo(MovieInfo mi)
        {
            return MapXmlToObject(mi);
        }
        /// <summary>
        /// Parses the movie filename.
        /// </summary>
        /// <param name="filename">The filename.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public  MovieInfo ParseMovieFilename(string filename)
        {
            var rx = new Regex(@"(?<title>.{2,})(?<year>\d{4})");
            var removeSpecChar = new Regex(@"[^a-zA-Z0-9!&$-]"); 
            
            var ptags = new Regex(
                @" cam |.cam.|dvdrip|r5.line|.TS.|.wp.|screener|dvdscr|.scr.| scr |dvdscreener|r5|dvdr|bdrip|hdtv|.tc.| tc |telecine|vhsrip|divx|bluray|hddvd|.internal.| internal |xvid|pdtv|dvd9", 
                RegexOptions.IgnoreCase
                );


            int year;
            string title = string.Empty;
            if (rx.IsMatch(filename))
            {
                var msa = rx.Match(filename).Value;
                string ystring = msa.Trim().Substring(msa.Length - 4,4);
                int.TryParse(ystring, out year);

                              
                
                msa = msa.Substring(0, msa.Length - 4);
                msa = removeSpecChar.Replace(msa, " ").Trim();

                msa = NormalizeStringSpacing(msa);
                
                title = msa;


            }
            else
            {
                year = DateTime.Now.Year;
                title = ptags.Split(filename)[0];
                title = removeSpecChar.Replace(title, " ");
               
            }

            title = NormalizeStringSpacing(title);


            return new MovieInfo { ReleaseYear = year, Title = title };
        }
        /// <summary>
        /// Builds the query URL.
        /// </summary>
        /// <param name="mi">The mi.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public string BuildQueryUrl(MovieInfo mi)
        { 

            string title = mi.Title.Replace(" ", "+");

            return String.Format("http://tagchimp.com/ape/lookup.php?token={0}&type=search&title={1}&totalChapters=X&locked=IsLocked&videoKind=Movie", ApiKey, title);
        }
        /// <summary>
        /// Normalizes the string spacing.
        /// </summary>
        /// <param name="ss">The ss.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        private static string NormalizeStringSpacing(string ss)
        {
            var ws = new Regex(@"\s");
            var strarr = ws.Split(ss).ToList();

            for (int i = 0; i < strarr.Count; ++i)
            {

                if (String.IsNullOrEmpty(strarr[i]) || strarr[i].Trim().Length == 0)
                {
                    strarr.RemoveAt(i);
                }
            }
            
            var sb = new StringBuilder();
            strarr.ForEach(word => sb.Append(word.Length > 0 ? String.Format("{0} ", word) : ""));

            return sb.ToString().Trim();
        }
        /// <summary>
        /// Gets the most complete result from a list of movies with the same title and year.
        /// </summary>
        /// <param name="movies">The movies to filter.</param>
        /// <returns></returns>
        private MovieInfo getBestResult(List<MovieInfo> movies)
        {
            
            if (movies == null)
                throw new ArgumentNullException("movies", "movies is null.");
            int index = 0;
            int pcount = 0;

            movies.ForEach(m =>
            {
                var t = m.GetType();
                var props = t.GetProperties();
                int i = 0;
                Array.ForEach(props, p =>
                {
                    int count = 0;
                    object value = p.GetValue(m, null);
                    if (value is string)
                    {
                        if (!string.IsNullOrEmpty((string)value))
                            count++;
                    }
                    else
                        if (value is int)
                        {
                            if ((int)value > 0)
                                count++;
                        }
                        else
                            if (value is List<string>)
                                count += ((List<string>)value).Count;
                    if (count > pcount)
                    {
                        pcount = count;
                        index = i;
                    }
                });
                    i++;
            });

            return movies[index];
        }
        #endregion
        #region Movie / TV Show  Data submitter 

        //TODO: Submit data logic 
        


        #endregion

    }


}